/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Cleiton
 */
abstract class RoteadorImpl extends UnicastRemoteObject implements Roteador {
    
    private boolean bellman;
    private String name;
    private HashMap<String, Host> hosts;
    private HashMap<String, Roteador> roteadoresVizinhos;
    private Gerenciador servidor;
    private No no;
    /**
     * Configuração da rede contendo todos os pesos
     */
    private String[][] rede; //que vai receber a rede[][] do servidor
    private int[][] matrizCalculo; //que vai conter os pesos
    private CompatibilizaMatriz converteMatriz;
    private AlgoritmoDijkstra dijkstra;
    //  private int[] antecessor;//o caminho de se chegar aos demais roteadores

    public RoteadorImpl() throws RemoteException {
        roteadoresVizinhos = new HashMap<String, Roteador>();
        hosts = new HashMap<String, Host>();
    }
    
    public String getIp() {
        InetAddress address = null;
        try {
            address = Inet4Address.getLocalHost();
        } catch (UnknownHostException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return address.getHostAddress();
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public void addHost(String name, String ipHost) throws RemoteException {
        Host conectarHost = conectarHost(name, ipHost);
        hosts.put(name, conectarHost);
    }

    /***
     * Conectar o Host ao servidor.
     */
    public void conectarServidor() {
        try {
            servidor = (Gerenciador) Naming.lookup("rmi://" + HostImpl.SERVIDORCENTRAL + "/Servidor");
        } catch (NotBoundException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**Conecta Host ao seu Roteador gateway
     * 
     */
    public Roteador conectarRoteador(String name, String ipRoteador) {
        Roteador roteador = null;
        try {
            roteador = (Roteador) Naming.lookup("rmi://" + ipRoteador + "/" + name);
        } catch (NotBoundException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return roteador;
    }

    /**
     *
     */
    public void recebeTabelaDoServidor() {
        converteMatriz = new CompatibilizaMatriz(rede);
        matrizCalculo = converteMatriz.preencheMatrizCalculo();//temos a matriz para os cálculos de Dijkstra e DV
    }
    
    private void enviarDjkst() {
        // int raiz=0;
        int raiz = converteMatriz.parsInt(name); //é o identificador do roteador
        dijkstra = new AlgoritmoDijkstra(matrizCalculo, raiz, matrizCalculo.length);
        dijkstra.dijkstra();//executa o algoritmo de dikstra
    }
    
    void servidorCentarlAlteraNomeHost() {
        try {
            String nameRoteador = servidor.receber_roteador(this.getIp());
            this.setNome(nameRoteador);
        } catch (RemoteException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private void setNome(String nameRoteador) throws RemoteException {
        name = nameRoteador;
    }
    
    @Override
    public void setRede(String[][] rede) throws RemoteException {
        this.rede = rede;
        this.recebeTabelaDoServidor();
    }

    /**
     * Adiciona todos os roteadores vizinhos
     * @param name do roteador vizinho
     * @param ip do roteador vizinho.
     * @throws RemoteException 
     */
    @Override
    public void addVizinhos(String name, String ip) throws RemoteException {
        Roteador roteador = conectarRoteador(name, ip);
        roteadoresVizinhos.put(name, roteador);
    }
    
    @Override
    public void receberMensagem(Pacote pacote) throws RemoteException {
        if (pacote.getTTL() == 0) {
            servidor.estourouTtl(this.getName());
        } else {
            String roteadorDestinoFinal = pacote.getNameDestino().split("-")[0];
            if (this.getName().equals(roteadorDestinoFinal)) {
                String hostDestino = pacote.getNameDestino().split("-")[1];
                Host host = hosts.get(hostDestino);
                if (host != null) {
                    servidor.receberInformação(this.getName(), pacote.getNameDestino(), false);
                    envieiPara(pacote, "Minha Rede para " + hostDestino);
                    host.receberMensagem(pacote);
                } else {
                    envieiPara(pacote, "Host não esta mais conectado na rede");
                }
            } else {
                if (bellman) {
                    System.out.println("Bell");
                    executaBell( roteadorDestinoFinal, pacote);
                } else {
                    executarDijkstra(pacote, roteadorDestinoFinal);
                }
                
            }
        }
    }

    /**
     * Envia mensagem para um roteador vizinho.
     * @param pacote pacote a ser enviado
     * @param nameRoteador 
     */
    private void enviarMensagemRoteador(Pacote pacote, String nameRoteador) {
        pacote.setTtl();
    }
    
    public Host conectarHost(String name, String Ip) {
        try {
            Host host;
            host = (Host) Naming.lookup("rmi://" + Ip + "/" + name);
            return host;
        } catch (NotBoundException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
    
    @Override
    public void alterarRede() throws RemoteException {
        roteadoresVizinhos = new HashMap<String, Roteador>();
        hosts = new HashMap<String, Host>();
    }
    
    private void executarDijkstra(Pacote pacote, String roteadorDestinoFinal) throws RemoteException {
        enviarDjkst();
        int numeroRoteadorDestinoFinal = converteMatriz.parsInt(roteadorDestinoFinal);
        int origem = converteMatriz.parsInt(name);
        int enviarpara = dijkstra.enviarpara(origem, numeroRoteadorDestinoFinal);
        String roteadorDestin = converteMatriz.parsString(enviarpara);
        this.enviarPara(roteadorDestin, pacote);
        
    }
    
    private void enviarPara(String roteadorDestino, Pacote pacote) throws RemoteException {
        pacote.setTtl();
        envieiPara(pacote, roteadorDestino);
        servidor.atualizarCaminho(this.getName(), roteadorDestino);
        servidor.receberInformação(this.getName(), roteadorDestino, true);
        Roteador get = roteadoresVizinhos.get(roteadorDestino);
        get.receberMensagem(pacote);
    }
    
    private void executaBell(String roteadorDestino, Pacote pacote) throws RemoteException {
        String paraquemenviar = no.paraquemenviar(roteadorDestino);
        enviarPara(paraquemenviar, pacote);
    }
    
    @Override
    public void alterarAlgoritmo(boolean beelmanFord) throws RemoteException {
        this.bellman = beelmanFord;
        if (bellman) {
            alterarName("BellMan Ford " + this.getName());
        } else {
            alterarName("Dijkstra " + this.getName());
        }
        
    }
    
    abstract void alterarName(String name);
    
    abstract void envieiPara(Pacote pacote, String envieiPara);
    
    public void criarNo(Integer vizinhos[], String nomes[]) throws RemoteException {
        no = new No(this.getName(), vizinhos, nomes);
    }
    
    @Override
    public void executaBellman() throws RemoteException {
        Integer[] vetorpraenviar = no.vetorpraenviar();
        if (vetorpraenviar != null) {
            //Enviando para todos os meus vizinhos
            for (String vizinho : roteadoresVizinhos.keySet()) {
                System.out.println("Estou mandando para " + vizinho);
                Roteador vizinh = roteadoresVizinhos.get(vizinho);
                vizinh.recebevetordovizinho(this.getName(), vetorpraenviar);
            }
        }
        no.setMudou(false);
    }
    
    public void recebevetordovizinho(String novizinho, Integer vetorVizinho[]) throws RemoteException {
        no.recebevetordovizinho(novizinho, vetorVizinho);
        Integer[] vetorVizinho1 = no.getVetorVizinho();

          if (no.getmudou()) {
        no.setMudou(false);
        for (String vizinho : roteadoresVizinhos.keySet()) {
        Roteador vizinh = roteadoresVizinhos.get(vizinho);
        vizinh.recebevetordovizinho(this.getName(), no.getVetorVizinho());
        }
        }
        
    }
}
